//
//  Empty.swift
//  RaveLand
//
//  Created by zhuxuhong on 2021/4/30.
//  Copyright © 2021 CSLY. All rights reserved.
//

import UIKit
import BWListKit
import BWToolKit

public typealias EmptyData = Empty.Data
public typealias EmptySets = Set<Empty.SenarioSet>

public enum Empty {
    public enum Senario: Equatable, Hashable {
        case noData
        case networkError
        case requestFailed
        case custom(AnyHashable)
        
        public static func == (lhs: Self, rhs: Self) -> Bool {
            return lhs.type == rhs.type
        }
        var type: String {
            switch self {
            case .custom(let type): return "\(type)"
            default: return "\(self)"
            }
        }
        public func hash(into hasher: inout Hasher) {
            hasher.combine(type)
        }
    }
    
    enum EmpyASKey {
        static var noData = "noData"
        static var networkError = "networkError"
        static var requestFailed = "requestFailed"
        static var custom = "custom"
    }

    public static func binds(_ sets: EmptySets, for listAdapter: BWListAdapter) {
        for s in sets {
            if fetch(s.senario, for: listAdapter) == nil {
                save(s, for: listAdapter)
            }
        }
    }
    static func save(_ set: SenarioSet, for listAdapter: BWListAdapter) {
        switch set.senario {
        case .noData:
            Objcer.setASValue(set, for: listAdapter, key: &EmpyASKey.noData, policy: .atomicRetain)
        case .networkError:
            Objcer.setASValue(set, for: listAdapter, key: &EmpyASKey.networkError, policy: .atomicRetain)
        case .requestFailed:
            Objcer.setASValue(set, for: listAdapter, key: &EmpyASKey.requestFailed, policy: .atomicRetain)
        case .custom(let type):
            let customSet = Objcer.getASValue(for: listAdapter, key: &EmpyASKey.custom) as? CustomSenarioSet ?? .init()
            customSet.sets[type] = set
            Objcer.setASValue(customSet, for: listAdapter, key: &EmpyASKey.custom, policy: .atomicRetain)
        }
    }
    public static func showing(for listAdapter: BWListAdapter) -> Senario? {
        guard let set = listAdapter.data?.sections?.first?.items?.first?.data as? SenarioSet else {
            return nil
        }
        return set.senario
    }
    public static func reload(for listAdapter: BWListAdapter){
        guard let senario = showing(for: listAdapter) else {
            return
        }
        reload(senario, for: listAdapter)
    }
    static func fetchCustoms(for listAdapter: BWListAdapter) -> [AnyHashable: SenarioSet] {
        guard let custom = Objcer.getASValue(for: listAdapter, key: &EmpyASKey.custom) as? CustomSenarioSet else {
            return [:]
        }
        return custom.sets
    }
    static func fetch(_ senario: Senario, for listAdapter: BWListAdapter) -> SenarioSet? {
        switch senario {
        case .noData:
            return Objcer.getASValue(for: listAdapter, key: &EmpyASKey.noData) as? SenarioSet
        case .networkError:
            return Objcer.getASValue(for: listAdapter, key: &EmpyASKey.networkError) as? SenarioSet
        case .requestFailed:
            return Objcer.getASValue(for: listAdapter, key: &EmpyASKey.requestFailed) as? SenarioSet
        case .custom(let type):
            return fetchCustoms(for: listAdapter)[type]
        }
    }
    public static func reload(_ senario: Senario, for listAdapter: BWListAdapter) {
        show(senario, for: listAdapter)
    }
    public static func update(_ senario: Senario, data: Empty.Data, for listAdapter: BWListAdapter) {
        guard let set = fetch(senario, for: listAdapter) else {
            return
        }
        set.data = data
        save(set, for: listAdapter)
        show(senario, for: listAdapter)
    }
    /// Temporarily showing, will not store in `listAdapter`
    public static func show(_ data: Empty.Data, for listAdapter: BWListAdapter) {
        _show(data, for: listAdapter)
    }
    
    private static func _show(_ data: EmptyDataWrapper, for listAdapter: BWListAdapter) {
        var list: BWList?
        if let tableView = listAdapter.tableView {
            list = .TableView(component: .cell(height: tableView.bounds.height, data: data))
        }
        else if let collectionView = listAdapter.collectionView {
            list = .CollectionView(component: .cell(height: collectionView.bounds.height, data: data))
        }
        guard let register = list?.register, let item = list?.cell else {
            return
        }
        listAdapter.data = .init(registers: [register], sections: [
            .init(items: [item])
        ])
    }
    /// Showing the binded senario empty in `listAdapter`
    public static func show(_ senario: Senario, for listAdapter: BWListAdapter) {
        guard let set = fetch(senario, for: listAdapter) else {
            return
        }
        _show(set, for: listAdapter)
    }
    public static func remove(_ senario: Senario, for listAdapter: BWListAdapter) {
        guard let set = fetch(senario, for: listAdapter) else {
            return
        }
        switch set.senario {
        case .noData:
            Objcer.removeASValue(for: listAdapter, key: &EmpyASKey.noData, policy: .atomicRetain)
        case .networkError:
            Objcer.removeASValue(for: listAdapter, key: &EmpyASKey.networkError, policy: .atomicRetain)
        case .requestFailed:
            Objcer.removeASValue(for: listAdapter, key: &EmpyASKey.requestFailed, policy: .atomicRetain)
        case .custom(let type):
            guard
                let sets = Objcer.getASValue(for: listAdapter, key: &EmpyASKey.custom) as? CustomSenarioSet, let _ = sets.sets[type] else {
                return
            }
            sets.sets.removeValue(forKey: type)
            Objcer.setASValue(sets, for: listAdapter, key: &EmpyASKey.custom, policy: .atomicRetain)
        }
    }
    public static func bwListItem(for list: BWList) -> BWListItem? {
        return list.cell
    }
    public static func bwListHeaderFooter(for list: BWList) -> BWListHeaderFooter? {
        return list.headerFooter
    }
    public static func showCustom(_ emptyView: UIView, on view: UIView) {
        emptyView.frame = view.bounds
        emptyView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(emptyView)
    }
}

extension Empty {
    public static func _showComponent(
        component: BWList.Component,
        for listAdapter: BWListAdapter,
        section: Int
    ) {
        let listData = listAdapter.data
        var registers = listData?.registers ?? []
        let sections = listData?.sections ?? []
        guard section < sections.count else {
            return
        }
        var list: BWList?
        
        if listAdapter.tableView != nil {
            list = BWList.TableView(component: component)
        }
        else if listAdapter.collectionView != nil {
            list = BWList.CollectionView(component: component)
        }
        
        let register = list?.register
        
        guard let register = register else {
            return
        }
        
        if !registers.contains(register) {
            registers.append(register)
            listAdapter.reloadRegisters(registers)
        }
        
        var emptyItem: BWListItem?
        var emptyHeaderFooter: BWListHeaderFooter?
        
        switch component {
        case .cell(_,_):
            emptyItem = list?.cell
            sections[section].items = [
                emptyItem
            ].compactMap{ $0 }
            
        case .header(_,_):
            emptyHeaderFooter = list?.headerFooter
            sections[section].header = emptyHeaderFooter
            
        case .footer(_,_):
            emptyHeaderFooter = list?.headerFooter
            sections[section].footer = emptyHeaderFooter
        }
        
        if emptyItem == nil, emptyHeaderFooter == nil {
            return
        }
        
        if let tableView = listAdapter.tableView {
            tableView.reloadSections(.init(integer: section), with: .automatic)
        }
        else if let collectionView = listAdapter.collectionView {
            collectionView.reloadSections(.init(integer: section))
        }
    }
    
    public static func showCell(
        for listAdapter: BWListAdapter,
        section: Int,
        height: CGFloat = UIScreen.main.bounds.height/2,
        data: EmptyData
    ) {
        _showComponent(component: .cell(height: height, data: data), for: listAdapter, section: section)
    }
    public static func showHeader(
        for listAdapter: BWListAdapter,
        section: Int,
        height: CGFloat = UIScreen.main.bounds.height/2,
        data: EmptyData
    ) {
        _showComponent(component: .header(height: height, data: data), for: listAdapter, section: section)
    }
    public static func showFooter(
        for listAdapter: BWListAdapter,
        section: Int,
        height: CGFloat = UIScreen.main.bounds.height/2,
        data: EmptyData
    ) {
        _showComponent(component: .footer(height: height, data: data), for: listAdapter, section: section)
    }
}
